package io.gitee.pkmer.core.base32;

import org.apache.commons.codec.binary.Base32;

import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import static io.gitee.pkmer.util.NumUtil.getBigPrime;

/**
 * @author <a href="mailto:1193094618@qq.com">pkmer</a>
 * <br>
 * <a href = "https://gitee.com/pkmer/sonatype-central-publisher">Code Repository</a>
 * At 2024/8/26
 * base32 签名
 */
public class SignBase32 {

    private static final long DEFAULT_SEED = 123456789L;

    private static final int FRESH_BIT = 4;
    private static final int FRESH_BIT_MASK = (1 << FRESH_BIT) - 1;
    private static final int XOR_BIT = 5;
    private static final int XOR_BIT_MASK = (1 << XOR_BIT) - 1;
    private static final int SERIAL_NUM_BIT = 32;
    private static final int SERIAL_NUM_BIT_MASK = 0xFFFFFFFF;

    private static final int SIGN_CODE_MASK = 0x3FFF;
    private static final long PAYLOAD_MASK = 0xFFFFFFFFFL;

    private final List<Long> XOR_TABLE;

    private final Base32Plus base32;

    public SignBase32() {
        this(Base32Plus.builder().get());
    }

    public SignBase32(Base32Plus base32) {
        this(base32, DEFAULT_SEED);
    }

    public SignBase32(Base32Plus base32, long seed) {
        this.base32 = base32;
        this.XOR_TABLE = getBigPrime(1 << XOR_BIT, FRESH_BIT + SERIAL_NUM_BIT, seed);
    }


    /**
     * 加权密钥表
     * todo 用properties优化一下
     */
    private static final List<List<Integer>> WEIGHTED_KEY_TABLE = List.of(
            List.of(88, 17, 8, 55, 24, 91, 81, 68, 88, 94),
            List.of(51, 22, 82, 44, 8, 39, 90, 65, 45, 62),
            List.of(8, 57, 82, 40, 55, 11, 6, 22, 45, 62),
            List.of(23, 67, 58, 96, 32, 49, 73, 88, 67, 18),
            List.of(17, 79, 88, 94, 6, 15, 99, 21, 65, 34),
            List.of(4, 98, 88, 75, 70, 93, 95, 24, 39, 49),
            List.of(33, 2, 81, 98, 59, 51, 22, 24, 44, 8),
            List.of(32, 35, 13, 53, 97, 30, 51, 90, 63, 55),
            List.of(43, 58, 81, 86, 94, 26, 67, 5, 75, 70),
            List.of(39, 48, 83, 75, 19, 1, 63, 50, 48, 24),
            List.of(54, 50, 65, 71, 35, 62, 26, 63, 6, 22),
            List.of(95, 74, 48, 24, 26, 49, 96, 23, 55, 73),
            List.of(6, 67, 18, 45, 62, 85, 60, 95, 30, 51),
            List.of(50, 68, 63, 55, 73, 83, 25, 76, 79, 88),
            List.of(21, 53, 65, 34, 50, 30, 71, 79, 21, 65),
            List.of(2, 11, 25, 39, 49, 76, 12, 61, 96, 23)
    );

    /**
     * int 序列号 32bit
     * fresh 新鲜值 4bit
     * 序列号 + fresh = payload
     * 加权的14bit  密钥
     *
     * @param num 序列号
     */
    public String generateCode(int num) {

        if (num < 0) {
            throw new UnsupportedOperationException("Unsupported negative number at current");
        }

        // 4bit
        byte fresh = (byte) (num & FRESH_BIT_MASK);

        long payload = ((long) fresh << SERIAL_NUM_BIT) | num;

        // 准备取 14bit
        int signCode = calcSign(fresh, payload);

        int xorIndex = signCode & XOR_BIT_MASK;
        Long bigPrime = XOR_TABLE.get(xorIndex);

        // 数据混淆
        payload ^= bigPrime;

        // 拼接最后的结果 签名(14bit) + payload(36bit)
        long result = ((long) signCode << (FRESH_BIT + SERIAL_NUM_BIT)) | payload;

        return base32.encode(result);
    }


    /**
     * 解密出原始的序列号
     * @param encode 加密的字符串
     * @return 原始的序列号
     */
    public int getSerialNum(String encode) {
        // 50bit
        long value = base32.decodeForLong(encode);
        // signCode 14bit
        long signCode = (value >>> (FRESH_BIT + SERIAL_NUM_BIT)) & SIGN_CODE_MASK;

        int XOR_INDEX = (int) (signCode & ((1 << XOR_BIT) - 1));

        // payload 36 bit
        long payload = value & PAYLOAD_MASK;

        // 真实的payload
        payload ^= XOR_TABLE.get(XOR_INDEX);

        byte fresh = (byte) (payload >>> SERIAL_NUM_BIT & FRESH_BIT_MASK);

        int checkSign = calcSign(fresh, payload);

        if (signCode != checkSign) {
            throw new RuntimeException("Oops Sign Code Error");
        }

        // 直接用int强行截取32bit
        return (int) payload;
    }


    /**
     * 计算签名
     *
     * @param fresh   新鲜值
     * @param payload fresh + 序列号 （32bit + 4bit）
     * @return 签名
     */
    private static int calcSign(byte fresh, long payload) {
        List<Integer> table = WEIGHTED_KEY_TABLE.get(fresh);
        final int unit = 4;
        int i = 0;
        int sign = 0;
        final int TOTAL = (FRESH_BIT + SERIAL_NUM_BIT) / unit;
        while (i < TOTAL) {
            int value = (int) (payload & FRESH_BIT_MASK);
            Integer weight = table.get(i);
            sign += value * weight;
            payload >>>= unit;
            i++;
        }

        return sign & SIGN_CODE_MASK;
    }
}
